﻿using System.Collections;
using System.Collections.Generic;
using SystemDebug = System.Diagnostics.Debug;
using JetBrains.Annotations;

using UnityEngine;


[ExecuteInEditMode]
[RequireComponent( typeof( Camera ) )]
[AddComponentMenu( "Image Effects/Blur/Blur for Popup" )]
public class Popup_Blur : MonoBehaviour
{
	[PublicAPI]
	public bool PreDownsample; // "false" is good, for quality.

	[PublicAPI]
	[Range( 1, 4 )]
	public int BlurIterations = 1;

	[PublicAPI]
	[Range( 0, 4 )]
	public int Downsample = 2;

	public enum EBlurType
	{
		StandardGauss = 0,
		[UsedImplicitly]
		SgxGauss = 1,
	}

	[PublicAPI]
	public EBlurType BlurType = EBlurType.StandardGauss;

	[PublicAPI]
	[Range( 0.0f, 10.0f )]
	public float BlurSize = 3f;

	private RenderTexture _captured_screen;
	private Material _material;
	private int _downsampled_width;
	private int _downsampled_height;


	public static void ClearAll()
	{
		//!!TODO : 모든 camera에서 blur효과 없애는 것 구현
	}


    public static Popup_Blur Activate(Camera camera, int iterations)
    {
        SystemDebug.Assert(camera);    
        var blur_effect = camera.gameObject.MakeComponent<Popup_Blur>();
        blur_effect.enabled = true;
        blur_effect.BlurIterations = iterations;
        return blur_effect.enabled ? blur_effect : null;
    }

	public static Popup_Blur Activate( Camera camera )
	{
        return Activate(camera, 1);
	}

	public static void Deactivate( Camera camera )
	{
		SystemDebug.Assert( camera );

		var blur_effect = camera.gameObject.GetComponent<Popup_Blur>();
		if( blur_effect )
			blur_effect.enabled = false;
	}


	public static Popup_Blur GetActivated( Camera camera )
	{
		SystemDebug.Assert( camera );
		return camera.gameObject.GetComponent<Popup_Blur>();
	}


	public static void AddBackground( Popup_Blur blur, Camera camera )
	{
		if( !camera )
			return;
		blur._AssureRenderTexture();
		if( camera.targetTexture == null )
		{
			camera.targetTexture = blur._captured_screen;
		}
	}


	public static void RemoveBackground( Popup_Blur blur, Camera camera )
	{
		if( !camera )
			return;

		if( camera.targetTexture == blur._captured_screen )
		{
			camera.targetTexture = null;
		}
	}


	[UsedImplicitly]
	internal void OnEnable()
	{
		SystemDebug.Assert( camera );

		// render texture 생성
		_AssureRenderTexture();

		// material 생성
		var shader = Shader.Find( "Hidden/FastBlur" );
		SystemDebug.Assert( shader );
		_material = new Material( shader ) { hideFlags = HideFlags.DontSave };
		SystemDebug.Assert( _material );
		if( !_material )
		{
			enabled = false;
		}
	}


	[UsedImplicitly]
	internal void OnDisable()
	{
		if( _captured_screen )
		{
			_captured_screen.Release();
			//RenderTexture.ReleaseTemporary( _captured_screen );
			_captured_screen = null;
		}

		if( _material )
		{
			Destroy( _material );
			_material = null;
		}
	}


	private void _AssureRenderTexture()
	{
		SystemDebug.Assert( camera );
		if( _captured_screen )
			return;

		// 해상도 결정
		int width = (int)camera.pixelWidth;
		int height = (int)camera.pixelHeight;
		_downsampled_width = width >> Downsample;
		_downsampled_height = height >> Downsample;
		if( PreDownsample )
		{
			width = _downsampled_width;
			height = _downsampled_height;
		}

		// capture용 render texture 생성
		_captured_screen = new RenderTexture( width, height, 24, RenderTextureFormat.Default ) { filterMode = FilterMode.Bilinear };
		//_captured_screen = new RenderTexture( width, height, 24, RenderTextureFormat.Default ) { filterMode = FilterMode.Bilinear };
		//_captured_screen = RenderTexture.GetTemporary( width, height, 24, _captured_screen.format ) { filterMode = FilterMode.Bilinear };
	}


	/*
	internal void OnPreCull()
	{
	}
	*/

	[UsedImplicitly]
	internal void OnPreRender()
	{
		float width_mod = 1.0f / ( 1.0f * ( 1 << Downsample ) );
		var pass_offs = BlurType == EBlurType.StandardGauss ? 0 : 2;

		// 작업 텍스쳐 생성
		RenderTexture rt1;
		RenderTexture rt2;
		if( PreDownsample )
		{
			rt1 = _captured_screen;
			rt2 = RenderTexture.GetTemporary( _downsampled_width, _downsampled_height, 0, _captured_screen.format );
			rt2.filterMode = FilterMode.Bilinear;
		}
		else
		{
			rt1 = RenderTexture.GetTemporary( _downsampled_width, _downsampled_height, 0, _captured_screen.format );
			rt1.filterMode = FilterMode.Bilinear;
			rt2 = RenderTexture.GetTemporary( _downsampled_width, _downsampled_height, 0, _captured_screen.format );
			rt2.filterMode = FilterMode.Bilinear;

			Graphics.Blit( _captured_screen, rt1, _material, 0 );
		}

		for( int i = 0; i < BlurIterations; i++ )
		{
			float iteration_offs = ( i * 1.0f );
			_material.SetVector( "_Parameter", new Vector4( BlurSize * width_mod + iteration_offs, -BlurSize * width_mod - iteration_offs, 0.0f, 0.0f ) );

			// vertical blur
			//temp_texture.DiscardContents();
			Graphics.Blit( rt1, rt2, _material, 1 + pass_offs );
			// horizontal blur
			rt1.DiscardContents();
			Graphics.Blit( rt2, rt1, _material, 2 + pass_offs );
		}

		Graphics.Blit( rt1, (RenderTexture)null );
		if( rt1 != _captured_screen )
			RenderTexture.ReleaseTemporary( rt1 );
		RenderTexture.ReleaseTemporary( rt2 );
	}


	[UsedImplicitly]
	internal void OnPostRender()
	{
		_captured_screen.DiscardContents();
		//Graphics.Blit( _captured_screen, (RenderTexture)null );
	}

	/*
	internal void OnRenderImage( RenderTexture source, RenderTexture destination )
	{
		//Graphics.Blit( source, destination );
		Graphics.Blit( _captured_screen, destination );
	}
	*/
}
